//
// Created by lmliang.
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <linux/limits.h>
#include <libunwind.h>
#include "backtrace.h"

pthread_rwlock_t g_backtrace_handler_rwlock = PTHREAD_RWLOCK_INITIALIZER;;
backtrace_handler g_backtrace_handler = NULL;

// get executable name. returns 0 on success and -1 on error.
int exe_name(char* buf, int len)
{
    const char* path = "/proc/self/exe";
    return readlink(path, buf, len);
}

// execute shell get response. returns response lenght on success and -1 on error.
int exec_shell(const char* cmd, char* buf, int len)
{
    FILE* p = NULL;
    int rlen = 0;

    memset(buf, 0, len);

    p = popen(cmd, "r");
    if (NULL == p)
        return -1;

    rlen = fread(buf, 1, len, p);

    if (rlen == 0 && !feof(p))
        rlen = -1;

    fclose(p);
    return rlen;
}

// returns response lenght on success and -1 on error.
int get_addr_line(char* resp, char* buf, int len)
{
    char fun[512] = {0};
    char addr[512] = {0};
    char* p = resp;
    char* tok = NULL;

    memset(buf, 0, len);
    if (resp == NULL || strlen(resp) <= 0)
        return -1;

    tok = strsep(&p, "\n");
    while (tok != NULL)
    {
        strcat(buf, "\t");
        strcat(buf, tok);
        tok = strsep(&p, "\n");
    }

    return strlen(buf);
}

// exception signal handler.
void do_backtrace(int sig, siginfo_t* info, void* p)
{
    char frame[1024] = {0};
    char resp[1024] = {0};
    char exe[PATH_MAX] = {0};
    char buf[2048] = {0};

    unw_cursor_t cursor;
    unw_context_t context;
    unw_word_t offset;
    unw_word_t addr;

    int get_exe = exe_name(exe, PATH_MAX);

    pthread_rwlock_rdlock(&g_backtrace_handler_rwlock);

    if (g_backtrace_handler == NULL)
    {
        pthread_rwlock_unlock(&g_backtrace_handler_rwlock);
        exit(0);
    }

    sprintf(buf, "Signal: %s(%d) code(%d)", strsignal(info->si_signo), info->si_signo, info->si_code);

    g_backtrace_handler(buf);

    if (unw_getcontext(&context) == 0)
    {
        unw_init_local(&cursor, &context);

        while (unw_step(&cursor) > 0)
        {
            if (unw_get_reg(&cursor, UNW_REG_IP, &addr) != 0)
                break;

            memset(frame, 0, sizeof(frame));
            if (unw_get_proc_name(&cursor, frame, sizeof(frame), &offset) != 0)
                break;

            memset(buf, 0, sizeof(buf));
            sprintf(buf, "%p : (%s+0x%x) [%p]", addr, frame, offset, addr);
            g_backtrace_handler(buf);

            if (get_exe > 0)
            {
                memset(buf, 0, sizeof(buf));
                memset(resp, 0, sizeof(resp));
                sprintf(buf, "addr2line -f -C -s -e %s %p", exe, addr);
                if (exec_shell(buf, resp, sizeof(resp)) > 0)
                {
                    memset(buf, 0, sizeof(buf));
                    if (get_addr_line(resp, buf, sizeof(buf)) > 0)
                        g_backtrace_handler(buf);
                    else
                        g_backtrace_handler(resp);
                }
            }
        }
    }

    pthread_rwlock_unlock(&g_backtrace_handler_rwlock);

    exit(0);
}

int init_sig_action(backtrace_handler handler)
{
    if ( NULL == handler)
        return -1;

    pthread_rwlock_wrlock(&g_backtrace_handler_rwlock);
    g_backtrace_handler = handler;
    pthread_rwlock_unlock(&g_backtrace_handler_rwlock);

    struct sigaction act;
    act.sa_sigaction = do_backtrace;
    act.sa_flags = SA_RESTART | SA_SIGINFO;

    if (sigemptyset(&act.sa_mask) == -1)
        return -1;

    if (sigaction(SIGILL, &act, NULL) == -1)
        return -1;

    if (sigaction(SIGABRT, &act, NULL) == -1)
        return -1;

    if (sigaction(SIGFPE, &act, NULL) == -1)
        return -1;

    if (sigaction(SIGSEGV, &act, NULL) == -1)
        return -1;

    if (sigaction(SIGUSR1, &act, NULL) == -1)
        return -1;

    if (sigaction(SIGBUS, &act, NULL) == -1)
        return -1;

    if (sigaction(SIGSYS, &act, NULL) == -1)
        return -1;

    return 0;
}
